package org.opentcs.kcvehicle.communication.kc.udp.io;


import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import java.net.InetSocketAddress;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.access.rmi.KernelServicePortalBuilder;
import org.opentcs.common.GuestUserCredentials;
import org.opentcs.components.kernel.services.VehicleService;
import org.opentcs.data.model.Vehicle;
import org.opentcs.kcvehicle.communication.kc.common.Package;
import org.opentcs.kcvehicle.communication.kc.syn.AsyncFuture;
import org.opentcs.kcvehicle.communication.kc.syn.SendedList;
import org.opentcs.kcvehicle.communication.kc.udp.agv.codec.AgvUdpChannelInitializer;
import org.opentcs.kcvehicle.communication.kc.udp.agv.param.AgvEvent;
import org.opentcs.kcvehicle.communication.kc.udp.agv.param.rsp.RcvEventPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public enum UDPClient {
  //如果要配置多个链接， local1 local2 .... 这样排下去好了

  //localAGV("50", "192.168.124.124", 17804, 55678),
  localAGV("50", "localhost", 17804, 55678),
  ;

  // 服务端用户名+密码+地址+端口
  private String SERVICE_USER = GuestUserCredentials.USER;
  private String SERVICE_PWD = GuestUserCredentials.PASSWORD;
  private String SERVICE_HOST = GuestUserCredentials.IP;
  private Integer SERVICE_PORT = GuestUserCredentials.PORT;

  private String name;
  private String host;
  //默认 0   port
  private Integer port;
  private Integer bindPort;
  private Bootstrap bootstrap;
  private NioEventLoopGroup group;


  private Channel conn;
  private boolean isOnline;

//  private static final Logger logger = LoggerFactory.getLogger("AGVLOGGER");
  private static final Logger logger = LoggerFactory.getLogger(UDPClient.class);

  // ====  连接节点配置信息  =====   开始

  // ====  连接节点配置信息  =====   结束

  UDPClient() {
  }

  //coreSize 是线程池的数量
  UDPClient(String name, String host, Integer port, Integer bindPort) {
    this.name = name;
    this.host = host;
    this.port = port;
    this.conn = null;
    this.bindPort = bindPort;
    initClient();
  }


  public String getHost() {
    return this.host;
  }

  public String getName() {
    return this.name;
  }

  public void setIsOnline(boolean isOnline) {
    this.isOnline = isOnline;
  }

  /**
   * desc : 判断此链接 健康状况
   * return
   * true : 此tcp连接 正常
   * false : 此tcp连接 异常
   */
  public boolean isOnline() {
    return isOnline;
  }

  public void close() {
    //手动关闭连接，会出发InActivite 事件
    group.shutdownGracefully();
  }

  public static ByteBuf byteArrayToByteBuf(byte[] byteArray) {
    // 使用Unpooled类创建ByteBuf
    ByteBuf byteBuf = Unpooled.buffer(byteArray.length);
    // 将byte[]写入ByteBuf
    byteBuf.writeBytes(byteArray);
    return byteBuf;
  }

  /**
   * （--）线程安全的（--）
   *
   *
   * 如果返回null，就代表出现了异常，并且尝试了 retryMax 次数，并且尝试重置连接
   */
  public RcvEventPackage send(AgvEvent event) {
    try {
      //sendread 报文
      //应该是ReadRequestFrame 继承 Packet 类，然后直接  Tio.bSend(this.conn, ReadRequestFrame)

      Package mbPackage = event.toBytes();
      AsyncFuture<Package> add = SendedList.add(mbPackage.getTransationId(), null);

      if(this.conn == null || !this.conn.isActive()) {//如果连接断开，就重新连接
        initClient();
      }

      this.conn.writeAndFlush(
          new DatagramPacket(
              byteArrayToByteBuf(mbPackage.getBody()),
              new InetSocketAddress(this.host, this.port)
          )
      )
          .sync();

      Package aPackage = add.get(5000L, mbPackage.getTransationId());
      byte[] body = aPackage.getBody();

      String errMsg = "    [ AGVclient - send success ]  [   " + name + "   host: " + this.host
          + "   ]" + event.toString();
      logger.info(errMsg);
      //注意：这里的body 是整个 response结构，包括 : 授权码 + header + body
      return new RcvEventPackage(body[22], body);
    }
    catch (Throwable e) {
      //e.printStackTrace();
      String errMsg = "    [ AGVclient - Read ]  [   " + name + "   host: " + this.host
          + "   ]    (     occur err      ) errTime:  " + "  ;   send errMsg : " + e.getMessage()
          + "   ;  event :" + event.toString();
      logger.info(errMsg);
      throw new RuntimeException(errMsg);
    }
  }

  public void subscribeKC(RcvEventPackage rcv, byte[] body) {
    if (rcv.isOk()) {

      for (byte b : body) {
        System.out.print(byteToHex(b) + " ");
      }
      System.out.println();
      KernelServicePortal servicePortal = new KernelServicePortalBuilder(
          SERVICE_USER, SERVICE_PWD
      ).build();
      servicePortal.login(SERVICE_HOST, SERVICE_PORT);
      VehicleService vehicleService = servicePortal.getVehicleService();
      Vehicle vehicle = vehicleService.fetchObject(Vehicle.class, name);
      vehicleService.sendCommAdapterMessage(vehicle.getReference(), body);

      servicePortal.logout();
    }
    else {
      System.out.println();
      System.out.println("subscribe received transationId : " + "isok:" + rcv.isOk());
    }
  }

  public void subscribe0xB0(RcvEventPackage rcv, byte[] body) {
    if (rcv.isOk()) {
      this.achieveSub0xB0(body);
    }
    else {
      System.out.println();
      System.out.println("subscribe0xB0 received transationId : " + "isok:" + rcv.isOk());
    }
  }

  public void subscribe0xAF(RcvEventPackage rcv, byte[] body) {

    if (rcv.isOk()) {
      this.achieveSub0xAF(body);
    }
    else {
      System.out.println();
      System.out.println("subscribe0xAF received transationId : " + "isok:" + rcv.isOk());
    }
  }

  /**
   * 0xAF上报信息传入通讯适配器
   * @param body 响应数据
   */
  private void achieveSub0xAF(byte[] body) {
//    QueryRobotStatusRsp queryRobotStatusRsp = new QueryRobotStatusRsp(rcv.getDataBytes());
    KernelServicePortal servicePortal = new KernelServicePortalBuilder(
        SERVICE_USER, SERVICE_PWD
    ).build();
    servicePortal.login(SERVICE_HOST, SERVICE_PORT);
    VehicleService vehicleService = servicePortal.getVehicleService();
    Vehicle vehicle = vehicleService.fetchObject(Vehicle.class, name);
    vehicleService.sendCommAdapterMessage(vehicle.getReference(), body);
  }

  /**
   * 0xB0上报信息传入通讯适配器
   *
   * @param body 响应数据
   */
  private void achieveSub0xB0(byte[] body) {

//    QueryCargoStatusRsp queryCargoStatusRsp = new QueryCargoStatusRsp(rcv.getDataBytes());
//    System.out.println();
//    System.out.println("received subscribe 0xB0 List : " + "isok:" + rcv.isOk());
//    for (byte b : rcv.getValue()) {
//      System.out.print(byteToHex(b) + " ");
//    }

    KernelServicePortal servicePortal = new KernelServicePortalBuilder(
        SERVICE_USER, SERVICE_PWD
    ).build();
    servicePortal.login(SERVICE_HOST, SERVICE_PORT);
    VehicleService vehicleService = servicePortal.getVehicleService();
    Vehicle vehicle = vehicleService.fetchObject(Vehicle.class, name);

    if (vehicle == null) {
      throw new RuntimeException("vehicle name:" + name + ",does not exist");
    }

    //将AGV控制器上报信息传入通讯适配器
    vehicleService.sendCommAdapterMessage(vehicle.getReference(), body);

    servicePortal.logout();
  }

  public static void printInfo(AgvEvent agvEvent) {
    System.out.println("sended transationId : " + agvEvent.getTransationIdString());
    for (byte b : agvEvent.toBytes().getBody()) {
      System.out.print(byteToHex(b) + " ");
    }
  }

  public static String byteToHex(byte b) {
    // 将byte转换为无符号整数
    int unsignedByte = b & 0xFF;
    // 使用Integer.toHexString方法转换为十六进制字符串
    String hexString = Integer.toHexString(unsignedByte);
    // 如果字符串长度为1，需要在前面补0
    if (hexString.length() == 1) {
      return "0" + hexString;
    }
    return hexString;
  }

  /**
   * decs: 在项目启动的时候初始化 后面就不会初始化了
   */
  private void initClient() {
    //NioEventLoopGroup 、 Bootstrap 这些资源其实是不用 release的，因为全局共用一份的你释放了 下次还得再new
    NioEventLoopGroup group = new NioEventLoopGroup();
    this.bootstrap = new Bootstrap();
    this.bootstrap.group(group)
        /* 由于我们用的是UDP协议，所以要用NioDatagramChannel来创建 */
        .channel(NioDatagramChannel.class)
        .handler(new ChannelInitializer<NioDatagramChannel>() {
          @Override
          protected void initChannel(NioDatagramChannel ch)
              throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            //0 代表禁用 readerIdleTime 事件的监听
            //这两个是固定的
            //pipeline.addLast(new IdleStateHandler(0,0,10, TimeUnit.SECONDS));

            //协议层的handler 这里要做以区分

            pipeline.addLast(new AgvUdpChannelInitializer(UDPClient.this));
          }
        });
    try {
      this.conn = this.bootstrap.bind(this.bindPort).sync().channel();
    }
    catch (Exception e) {
      logger.info("AGV UDP Initial Exception : " + e.getMessage());
    }
  }


}
